#include <EB0/EB0.h>

namespace EB0
{
namespace Managers
{
#pragma region Constructor / Destructor
	// Constructor
	StateManager::StateManager(Window::RenderWindow* window)
	{
		// Set window
		mWindow = window;

		// Initial values
		mThreadBusy = false;
		mThread = 0;
		mPendingState = 0;
	}

	// Destructor
	StateManager::~StateManager()
	{
		// Wait for thread to finish then destroy it
		if (mThread)
		{
			mThread->Wait();
			delete mThread;
			mThread = 0;
		}

		// Remove Current states
		removeAllStates();

		// Empty Pending Queue
		mPending.clear();

		// Reset values
		mWindow = 0;
		mThreadBusy = false;
		mPendingState = 0;
	}
#pragma endregion

#pragma region Remove All States
	// Remove All States
	void StateManager::removeAllStates()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Stop and pop all states
		while (!mStates.empty())
		{
			mStates.top()->destroyScene(mWindow);
			mStates.top()->unload(mWindow);
			mWindow->removeFrameListener(mStates.top());
			mWindow->removeWindowListener(mStates.top());
			mStates.pop();
		}
	}
#pragma endregion

#pragma region Push State / Pop State / Change State
	// Push State
	void StateManager::pushState(States::GameState* state)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Add to queue if not null
		if (state)
			mPending.push_back(state);
	}

	// Pop State
	void StateManager::popState()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Add a null pointer to queue, indicating a pop command
		mPending.push_back(0);
	}

	// Pop all states
	void StateManager::popAllStates()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Count the number of states to pop from the queue
		// We will ignore pop requests as they may not always result in a popped stated
		// Additional pop requests when no states remain will be ignored; ultimately it does not matter
		// However, we don't want to arbitrarily pop a large number of states either :)
		int numRemove = 0;

		for (int pending = 0; pending < (signed)mPending.size(); pending++)
		{
			if (mPending.at(pending))
				numRemove++; // valid pointer, need to pop the state
		}

		// Add the current number of states for popping
		numRemove += (signed)mStates.size();

		// Pop numRemove states
		for (int pop = 0; pop < numRemove; pop++)
			popState();
	}

	// Change State
	void StateManager::changeState(States::GameState* state)
	{
		popState();
		pushState(state);
	}
#pragma region

#pragma region Update States
	// Update
	void StateManager::update(float dt)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Process queue
		if (!mPending.empty() && !mThreadBusy)
		{
			// We only deal with one pending item per tick

			// Push or Pop?
			if (mPending.front()) // not a null ponter or 0
			{
				// Pushing a state
				if (!mPendingState)
				{
					// Pause the current state
					if (!mStates.empty())
					{
						mStates.top()->pause(mWindow);

						// Remove listeners
						mWindow->removeFrameListener(mStates.top());
						mWindow->removeWindowListener(mStates.top());
					}

					// Set the state which will be acted upon
					// Note we haven't added the state to the stack yet!
					mPendingState = mPending.front();

					// Destroy previous thread if somehow it existed
					if (mThread)
					{
						mThread->Wait();
						delete mThread;
						mThread = 0;
					}

					// Launch thread
					mThread = new sf::Thread(&loadThread, this);
					mThread->Launch();
				}
				else
				{
					// Delete the thread, make sure it's finished
					if (mThread)
					{
						mThread->Wait();
						delete mThread;
						mThread = 0;
					}

					// The state has been loaded and NOW we can add it to the stack
					mStates.push(mPendingState);
					mStates.top()->createScene(mWindow);

					// Attach listeners
					mWindow->attachFrameListener(mStates.top());
					mWindow->attachWindowListener(mStates.top());

					// Pop the pending queue and reset the pending state
					mPending.erase(mPending.begin());
					mPendingState = 0;
				}
			}
			else
			{
				// Popping a state (null pointer indicates removal)
				if (!mPendingState)
				{
					// Are there any states left to pop?
					if (!mStates.empty())
					{
						// Destroy the current scene
						mStates.top()->destroyScene(mWindow);

						// Remove listeners
						mWindow->removeFrameListener(mStates.top());
						mWindow->removeWindowListener(mStates.top());

						// Set the state which will be acted upon
						// Note we haven't removed the state from the stack yet!
						mPendingState = mStates.top();

						// Destroy previous thread if somehow it existed
						if (mThread)
						{
							mThread->Wait();
							delete mThread;
							mThread = 0;
						}

						// Launch thread
						mThread = new sf::Thread(&unloadThread, this);
						mThread->Launch();
					}
					else
					{
						// Ignore any unnecessary pop requests
						while (!mPending.front())
							mPending.erase(mPending.begin());
					}
				}
				else
				{
					// Delete the thread, make sure it's finished
					if (mThread)
					{
						mThread->Wait();
						delete mThread;
						mThread = 0;
					}

					// Let's remove the current state from the stack
					if (!mStates.empty()) // this shouldn't be empty anyway...
						mStates.pop();

					// The state has been unloaded and removed NOW we can resume the previous state
					if (!mStates.empty())
					{
						// Resume state
						mStates.top()->resume(mWindow);

						// Attach listeners
						mWindow->attachFrameListener(mStates.top());
						mWindow->attachWindowListener(mStates.top());
					}

					// Pop the pending queue and reset the pending state
					mPending.erase(mPending.begin());
					mPendingState = 0;
				}
			}
		}

		// Run current state if there are no pending requests
		if (!mStates.empty() && mPending.empty()) // before was just (!mStates.empty())
			mStates.top()->update(mWindow, dt);
	}
#pragma endregion

#pragma region Thread
	// Generic loading thread (specify the StateManager via this pointer)
	void StateManager::loadThread(void* statemgr)
	{
		// Convert incoming data StateManager*
		StateManager* realStateMgr = static_cast<StateManager*>(statemgr);

		// Valid StateManager?
		if (realStateMgr)
		{
			// Set busy flag
			realStateMgr->mThreadBusy = true;

			// Load the state
			if (realStateMgr->mPendingState)
				realStateMgr->mPendingState->load(realStateMgr->mWindow);

			// No longer busy...
			realStateMgr->mThreadBusy = false;
		}
	}

	// Generic unloading thread (specify the StateManager via this pointer)
	void StateManager::unloadThread(void* statemgr)
	{
		// Convert incoming data StateManager*
		StateManager* realStateMgr = static_cast<StateManager*>(statemgr);

		// Valid StateManager?
		if (realStateMgr)
		{
			// Set busy flag
			realStateMgr->mThreadBusy = true;

			// Unload the state
			if (realStateMgr->mPendingState)
				realStateMgr->mPendingState->unload(realStateMgr->mWindow);

			// No longer busy...
			realStateMgr->mThreadBusy = false;
		}
	}
#pragma endregion
} // namespace Managers
}